part of "../time.dart";

/// 区别于DateTime,它只包含年月日,是以天为单位的,而舍去了具体的时间信息
/// 因为在规划很多长期的事情时,都是以天为单位的,如果使用包含太多信息的DateTime,反而会很麻烦
class Date implements Comparable<Date> {
  final int year;
  final int month;
  final int day;

  Date(this.year, this.month, this.day) {
    if (!_isValidDate(year, month, day)) {
      throw Bug('无效的日期: $year-$month-$day');
    }
  }

  factory Date.now() {
    final now = DateTime.now();
    return Date(now.year, now.month, now.day);
  }

  /// 目前仅支持从"yyyy-MM-dd"格式
  /// 如果不是这种格式,一律报错
  factory Date.parse(String str) {
    final parts = str.split('-');
    if (parts.length != 3) {
      throw Bug('无效的日期格式: $str');
    }
    final year = int.parse(parts[0]);
    final month = int.parse(parts[1]);
    final day = int.parse(parts[2]);
    return Date(year, month, day);
  }

  static Date? tryParse(String? str) {
    try {
      return Date.parse(str!);
    } catch (e) {
      return null;
    }
  }

  factory Date.fromDateTime(DateTime dateTime) => Date(
        dateTime.year,
        dateTime.month,
        dateTime.day,
      );

  /// 当两个日期的年月日完全相等时,则认为它们相等
  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is Date &&
        other.year == year &&
        other.month == month &&
        other.day == day;
  }

  /// 之前
  bool operator <(Date other) {
    if (year != other.year) return year < other.year;
    if (month != other.month) return month < other.month;
    return day < other.day;
  }

  /// 之前或相等
  bool operator <=(Date other) {
    return this == other || this < other;
  }

  /// 之后
  bool operator >(Date other) {
    return !(this <= other);
  }

  /// 之后或相等
  bool operator >=(Date other) {
    return !(this < other);
  }

  /// 将日期向前平移n天
  /// 相当于恰好和+相反
  Date operator -(int days) {
    final dateTime = DateTime(year, month, day).subtract(Duration(days: days));
    return Date(dateTime.year, dateTime.month, dateTime.day);
  }

  /// 将日期平移days天
  /// days: 正数表示向后移动,负数表示向前移动
  /// 不修改当前的Date,而是直接返回一个新的Date
  Date operator +(int days) {
    final dateTime = DateTime(year, month, day).add(Duration(days: days));
    return Date(dateTime.year, dateTime.month, dateTime.day);
  }

  /// 和另一个日期进行比较
  /// 比如：-1的意思就是"比别的日期少1天"
  int diff(Date other) {
    final thisDateTime = toDateTime();
    final otherDateTime = other.toDateTime();
    return thisDateTime.difference(otherDateTime).inDays;
  }

  @override
  int get hashCode => Object.hash(year, month, day);

  /// 统一为2024-01-20这种格式，这是可以被parse的格式
  /// 这里暂时不能改为format，因为weekday有依赖toString，而format依赖weekday，会造成栈溢出
  @override
  String toString() =>
      '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';

  /// 将某一天转换为Period
  Period toPeriod() {
    final begin = toDateTime();
    final end = begin + Duration(days: 1);
    return Period(begin, end);
  }

  /// 一天转换为DateTime时，就是抛弃了时分秒数据的DateTime
  DateTime toDateTime() => DateTime(year, month, day);

  /// 检查某个日期是否为真实有效的日期
  static bool _isValidDate(int year, int month, int day) {
    bool isLeapYear = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);

    int getDaysInMonth() {
      if (month == 2) {
        return isLeapYear ? 29 : 28;
      }
      const daysInMonth = [31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
      return daysInMonth[month - 1];
    }

    if (month < 1 || month > 12) return false;
    final daysInMonth = getDaysInMonth();
    if (day < 1 || day > daysInMonth) return false;
    return true;
  }

  /// 参照"format规则.md"
  String format(String pattern) => pattern
      .formatYear(year)
      .formatMonth(month)
      .formatDay(day)
      .formatWeekday(weekday);

  int get weekday => DateTime.parse(toString()).weekday;
  // 此方法会将一年末尾视为今年的第n周（但它有时候同时是下一年的第一周）
  int get weekNumber {
    // 计算该年的第一天是星期几
    int firstDayWeekday = Date(year, 1, 1).weekday;

    // 计算该日期是这一年的第几天
    int dayOfYear = diff(Date(year, 1, 1)) + 1;

    // 计算周数
    int weekNumber = ((dayOfYear + firstDayWeekday - 1) / 7).ceil();

    return weekNumber;
  }

  @override
  int compareTo(Date other) {
    if (year != other.year) return year.compareTo(other.year);
    if (month != other.month) return month.compareTo(other.month);
    return day.compareTo(other.day);
  }
}
